using System;
using System.Collections.Generic;
using System.Reflection;
using System.Threading;
using RaisingStudio.SmallProgram.Library;
using RaisingStudio.SmallBasicCompiler.Expressions;

namespace RaisingStudio.SmallBasicCompiler.Engine
{
    public class ProgramEngine : MarshalByRefObject
    {
        [Serializable]
        private class ProgramRunner
        {
            private int previousLineNumber = -1;
            public event EventHandler LineNumberChanged;
            public event EventHandler DebuggerStateChanged;
            public List<int> Breakpoints
            {
                get;
                private set;
            }
            public Instruction CurrentInstruction
            {
                get;
                private set;
            }
            public DebuggerCommand DebuggerCommand
            {
                get;
                private set;
            }
            public ManualResetEvent DebuggerExecution
            {
                get;
                private set;
            }
            public DebuggerState DebuggerState
            {
                get;
                private set;
            }
            public List<Instruction> Instructions
            {
                get;
                private set;
            }
            public Dictionary<string, Primitive> Fields
            {
                get;
                private set;
            }
            public Dictionary<string, int> LabelMap
            {
                get;
                private set;
            }
            public Dictionary<string, List<Instruction>> SubroutineInstructions
            {
                get;
                private set;
            }
            public TypeInfoBag TypeInfoBag
            {
                get;
                private set;
            }
            public ProgramRunner()
            {
                this.Breakpoints = new List<int>();
                this.TypeInfoBag = (AppDomain.CurrentDomain.GetData("TypeInfoBag") as TypeInfoBag);
                this.Instructions = (AppDomain.CurrentDomain.GetData("ProgramInstructions") as List<Instruction>);
                this.SubroutineInstructions = (AppDomain.CurrentDomain.GetData("SubroutineInstructions") as Dictionary<string, List<Instruction>>);
                this.Fields = new Dictionary<string, Primitive>();
                this.LabelMap = new Dictionary<string, int>();
                this.DebuggerExecution = new ManualResetEvent(true);
            }
            private void ChangeDebuggerState(DebuggerState state)
            {
                if (this.DebuggerState != state)
                {
                    this.DebuggerState = state;
                    EventHandler debuggerStateChanged = this.DebuggerStateChanged;
                    if (debuggerStateChanged != null)
                    {
                        debuggerStateChanged(this, EventArgs.Empty);
                    }
                }
            }
            private void CheckForExecutionBreak()
            {
                if (this.CurrentInstruction.LineNumber != this.previousLineNumber)
                {
                    if (this.Breakpoints.Contains(this.CurrentInstruction.LineNumber))
                    {
                        this.DebuggerExecution.Reset();
                    }
                    if (!this.DebuggerExecution.WaitOne(0))
                    {
                        this.ChangeDebuggerState(DebuggerState.Paused);
                        this.DebuggerExecution.WaitOne();
                    }
                }
                this.ChangeDebuggerState(DebuggerState.Running);
            }
            private void PrepareDebuggerForNextInstruction()
            {
                if (this.CurrentInstruction.LineNumber != this.previousLineNumber && (this.DebuggerCommand == DebuggerCommand.StepInto || this.DebuggerCommand == DebuggerCommand.StepOver))
                {
                    this.DebuggerExecution.Reset();
                }
            }
            public void Continue()
            {
                this.DebuggerExecution.Set();
            }
            public void Pause()
            {
                this.DebuggerExecution.Reset();
            }
            public void Reset()
            {
                this.Fields.Clear();
            }
            public void StepInto()
            {
                this.DebuggerCommand = DebuggerCommand.StepInto;
                this.Continue();
            }
            public void StepOver()
            {
                this.DebuggerCommand = DebuggerCommand.StepOver;
                this.Continue();
            }
            public void RunProgram(bool stopOnFirstInstruction)
            {
                if (stopOnFirstInstruction)
                {
                    this.DebuggerExecution.Reset();
                }
                new Thread(delegate()
                {
                    this.ExecuteInstructions(this.Instructions);
                }
                )
                {
                    IsBackground = true
                }.Start();
            }
            private void ExecuteInstructions(List<Instruction> instructions)
            {
                for (int i = 0; i < instructions.Count; i++)
                {
                    LabelInstruction labelInstruction = instructions[i] as LabelInstruction;
                    if (labelInstruction != null)
                    {
                        this.LabelMap[labelInstruction.LabelName] = i;
                    }
                }
                int j = 0;
                while (j < instructions.Count)
                {
                    this.CurrentInstruction = instructions[j];
                    if (this.CurrentInstruction.LineNumber != this.previousLineNumber && this.LineNumberChanged != null)
                    {
                        this.LineNumberChanged(this, EventArgs.Empty);
                    }
                    this.CheckForExecutionBreak();
                    string text = this.ExecuteInstruction(this.CurrentInstruction);
                    this.PrepareDebuggerForNextInstruction();
                    this.previousLineNumber = this.CurrentInstruction.LineNumber;
                    if (text == null)
                    {
                        j++;
                    }
                    else
                    {
                        j = this.LabelMap[text];
                    }
                }
                this.ChangeDebuggerState(DebuggerState.Finished);
            }
            private string ExecuteInstruction(Instruction instruction)
            {
                switch (instruction.InstructionType)
                {
                    case InstructionType.ArrayAssignment:
                        {
                            return this.ExecuteArrayAssignmentInstruction(instruction as ArrayAssignmentInstruction);
                        }
                    case InstructionType.EventAssignment:
                        {
                            return this.ExecuteEventAssignmentInstruction(instruction as EventAssignmentInstruction);
                        }
                    case InstructionType.FieldAssignment:
                        {
                            return this.ExecuteFieldAssignmentInstruction(instruction as FieldAssignmentInstruction);
                        }
                    case InstructionType.Goto:
                        {
                            return ((GotoInstruction)instruction).LabelName;
                        }
                    case InstructionType.IfGoto:
                        {
                            return this.ExecuteIfGotoInstruction(instruction as IfGotoInstruction);
                        }
                    case InstructionType.IfNotGoto:
                        {
                            return this.ExecuteIfNotGotoInstruction(instruction as IfNotGotoInstruction);
                        }
                    case InstructionType.LabelInstruction:
                        {
                            return null;
                        }
                    case InstructionType.MethodCall:
                        {
                            return this.EvaluateMethodCallInstruction(instruction as MethodCallInstruction);
                        }
                    case InstructionType.PropertyAssignment:
                        {
                            return this.ExecutePropertyAssignmentInstruction(instruction as PropertyAssignmentInstruction);
                        }
                    case InstructionType.SubroutineCall:
                        {
                            return this.ExecuteSubroutineCallInstruction(instruction as SubroutineCallInstruction);
                        }
                    default:
                        {
                            return null;
                        }
                }
            }
            private string ExecuteArrayAssignmentInstruction(ArrayAssignmentInstruction instruction)
            {
                this.SetArrayValue(instruction.ArrayExpression, this.EvaluateExpression(instruction.RightSide));
                return null;
            }
            private void SetArrayValue(ArrayExpression lvalue, Primitive value)
            {
                IdentifierExpression identifierExpression = lvalue.LeftHand as IdentifierExpression;
                if (identifierExpression != null)
                {
                    string normalizedText = identifierExpression.Identifier.NormalizedText;
                    Primitive array;
                    if (!this.Fields.TryGetValue(normalizedText, out array))
                    {
                        array = default(Primitive);
                    }
                    this.Fields[normalizedText] = Primitive.SetArrayValue(value, array, this.EvaluateExpression(lvalue.Indexer));
                    return;
                }
                ArrayExpression arrayExpression = lvalue.LeftHand as ArrayExpression;
                if (arrayExpression != null)
                {
                    this.SetArrayValue(arrayExpression, Primitive.SetArrayValue(value, this.EvaluateArrayExpression(arrayExpression), this.EvaluateExpression(lvalue.Indexer)));
                }
            }
            private string ExecuteEventAssignmentInstruction(EventAssignmentInstruction instruction)
            {
                instruction.EventInfo.AddEventHandler(null, new Action(() =>
                {
                    this.ExecuteInstructions(this.SubroutineInstructions[instruction.SubroutineName]);
                }));
                return null;
            }
            private string ExecuteFieldAssignmentInstruction(FieldAssignmentInstruction instruction)
            {
                this.Fields[instruction.FieldName] = this.EvaluateExpression(instruction.RightSide);
                return null;
            }
            private string ExecuteIfGotoInstruction(IfGotoInstruction instruction)
            {
                if (Primitive.ConvertToBoolean(this.EvaluateExpression(instruction.Condition)))
                {
                    return instruction.LabelName;
                }
                return null;
            }
            private string ExecuteIfNotGotoInstruction(IfNotGotoInstruction instruction)
            {
                if (!Primitive.ConvertToBoolean(this.EvaluateExpression(instruction.Condition)))
                {
                    return instruction.LabelName;
                }
                return null;
            }
            private string EvaluateMethodCallInstruction(MethodCallInstruction instruction)
            {
                this.EvaluateMethodCallExpression(instruction.MethodExpression);
                return null;
            }
            private string ExecutePropertyAssignmentInstruction(PropertyAssignmentInstruction instruction)
            {
                instruction.PropertyInfo.SetValue(null, this.EvaluateExpression(instruction.RightSide), null);
                return null;
            }
            private string ExecuteSubroutineCallInstruction(SubroutineCallInstruction instruction)
            {
                DebuggerCommand debuggerCommand = this.DebuggerCommand;
                if (this.DebuggerCommand == DebuggerCommand.StepOver)
                {
                    this.DebuggerCommand = DebuggerCommand.Run;
                }
                else
                {
                    if (this.DebuggerCommand == DebuggerCommand.StepInto)
                    {
                        this.Pause();
                    }
                }
                this.ExecuteInstructions(this.SubroutineInstructions[instruction.SubroutineName]);
                this.DebuggerCommand = debuggerCommand;
                return null;
            }
            private Primitive EvaluateExpression(Expression expression)
            {
                if (expression is ArrayExpression)
                {
                    return this.EvaluateArrayExpression(expression as ArrayExpression);
                }
                if (expression is BinaryExpression)
                {
                    return this.EvaluateBinaryExpression(expression as BinaryExpression);
                }
                if (expression is IdentifierExpression)
                {
                    return this.EvaluateIdentifierExpression(expression as IdentifierExpression);
                }
                if (expression is LiteralExpression)
                {
                    return this.EvaluateLiteralExpression(expression as LiteralExpression);
                }
                if (expression is MethodCallExpression)
                {
                    return this.EvaluateMethodCallExpression(expression as MethodCallExpression);
                }
                if (expression is PropertyExpression)
                {
                    return this.EvaluatePropertyExpression(expression as PropertyExpression);
                }
                if (expression is NegativeExpression)
                {
                    return this.EvaluateNegativeExpression(expression as NegativeExpression);
                }
                return default(Primitive);
            }
            private Primitive EvaluateArrayExpression(ArrayExpression expression)
            {
                IdentifierExpression identifierExpression = expression.LeftHand as IdentifierExpression;
                if (identifierExpression != null)
                {
                    Primitive array;
                    if (!this.Fields.TryGetValue(identifierExpression.Identifier.NormalizedText, out array))
                    {
                        array = "";
                    }
                    return Primitive.GetArrayValue(array, this.EvaluateExpression(expression.Indexer));
                }
                ArrayExpression arrayExpression = expression.LeftHand as ArrayExpression;
                if (arrayExpression != null)
                {
                    return Primitive.GetArrayValue(this.EvaluateArrayExpression(arrayExpression), this.EvaluateExpression(expression.Indexer));
                }
                return default(Primitive);
            }
            private Primitive EvaluateBinaryExpression(BinaryExpression expression)
            {
                Primitive primitive = this.EvaluateExpression(expression.LeftHandSide);
                Primitive primitive2 = this.EvaluateExpression(expression.RightHandSide);
                switch (expression.Operator.Token)
                {
                    case Token.And:
                        {
                            return Primitive.op_And(primitive, primitive2);
                        }
                    case Token.Equals:
                        {
                            return primitive.EqualTo(primitive2);
                        }
                    case Token.Or:
                        {
                            return Primitive.op_Or(primitive, primitive2);
                        }
                    case Token.Addition:
                        {
                            return primitive.Add(primitive2);
                        }
                    case Token.Subtraction:
                        {
                            return primitive.Subtract(primitive2);
                        }
                    case Token.Division:
                        {
                            return primitive.Divide(primitive2);
                        }
                    case Token.Multiplication:
                        {
                            return primitive.Multiply(primitive2);
                        }
                    case Token.LessThan:
                        {
                            return primitive.LessThan(primitive2);
                        }
                    case Token.LessThanEqualTo:
                        {
                            return primitive.LessThanOrEqualTo(primitive2);
                        }
                    case Token.GreaterThan:
                        {
                            return primitive.GreaterThan(primitive2);
                        }
                    case Token.GreaterThanEqualTo:
                        {
                            return primitive.GreaterThanOrEqualTo(primitive2);
                        }
                    case Token.NotEqualTo:
                        {
                            return primitive.NotEqualTo(primitive2);
                        }
                }
                return default(Primitive);
            }
            private Primitive EvaluateIdentifierExpression(IdentifierExpression expression)
            {
                Primitive result;
                if (this.Fields.TryGetValue(expression.Identifier.NormalizedText, out result))
                {
                    return result;
                }
                return default(Primitive);
            }
            private Primitive EvaluateLiteralExpression(LiteralExpression expression)
            {
                if (expression.Literal.Token == Token.StringLiteral)
                {
                    return new Primitive(expression.Literal.Text.Trim(new char[]
					{
						'"'
					}));
                }
                return new Primitive(expression.Literal.Text);
            }
            private Primitive EvaluateMethodCallExpression(MethodCallExpression expression)
            {
                TypeInfo typeInfo = this.TypeInfoBag.Types[expression.TypeName.NormalizedText];
                MethodInfo methodInfo = typeInfo.Methods[expression.MethodName.NormalizedText];
                List<object> list = new List<object>();
                foreach (Expression current in expression.Arguments)
                {
                    list.Add(this.EvaluateExpression(current));
                }
                object obj = methodInfo.Invoke(null, list.ToArray());
                if (obj is Primitive)
                {
                    return (Primitive)obj;
                }
                return default(Primitive);
            }
            private Primitive EvaluateNegativeExpression(NegativeExpression expression)
            {
                return -this.EvaluateExpression(expression.Expression);
            }
            private Primitive EvaluatePropertyExpression(PropertyExpression expression)
            {
                TypeInfo typeInfo = this.TypeInfoBag.Types[expression.TypeName.NormalizedText];
                PropertyInfo propertyInfo = typeInfo.Properties[expression.PropertyName.NormalizedText];
                object value = propertyInfo.GetValue(null, null);
                if (value is Primitive)
                {
                    return (Primitive)value;
                }
                return default(Primitive);
            }
        }
        public event EventHandler DebuggerStateChanged;
        public event EventHandler EngineUnloaded;
        public event EventHandler LineNumberChanged;
        public List<int> Breakpoints
        {
            get
            {
                return ProgramEngine.Engine.Breakpoints;
            }
        }
        public AppDomain DebuggerAppDomain
        {
            get;
            private set;
        }
        public Compiler Compiler
        {
            get;
            private set;
        }
        public DebuggerState CurrentDebuggerState
        {
            get;
            private set;
        }
        public Instruction CurrentInstruction
        {
            get;
            private set;
        }
        public static ProgramEngine Engine
        {
            get
            {
                return AppDomain.CurrentDomain.GetData("ProgramEngine") as ProgramEngine;
            }
        }
        public ProgramTranslator Translator
        {
            get;
            private set;
        }
        private static ProgramEngine.ProgramRunner Runner
        {
            get;
            set;
        }
        public ProgramEngine(Compiler compiler)
        {
            this.Compiler = compiler;
            this.Translator = new ProgramTranslator(this.Compiler);
            this.Translator.TranslateProgram();
            AppDomainSetup info = new AppDomainSetup
            {
                LoaderOptimization = LoaderOptimization.MultiDomain
            };
            this.DebuggerAppDomain = AppDomain.CreateDomain("Debuggee", null, info);
            this.DebuggerAppDomain.SetData("TypeInfoBag", this.Compiler.TypeInfoBag);
            this.DebuggerAppDomain.SetData("ProgramInstructions", this.Translator.ProgramInstructions);
            this.DebuggerAppDomain.SetData("SubroutineInstructions", this.Translator.SubroutineInstructions);
            this.DebuggerAppDomain.SetData("ProgramEngine", this);
            this.DebuggerAppDomain.DoCallBack(new CrossAppDomainDelegate(ProgramEngine.InitializeRunner));
            this.DebuggerAppDomain.DomainUnload += delegate
            {
                if (this.EngineUnloaded != null)
                {
                    this.EngineUnloaded(this, EventArgs.Empty);
                }
            }
            ;
        }
        private static void InitializeRunner()
        {
            ProgramEngine.Runner = new ProgramEngine.ProgramRunner();
            ProgramEngine.Runner.DebuggerStateChanged += delegate(object o, EventArgs e)
            {
                ProgramEngine.Engine.RaiseDebuggerStateChangedEvent(ProgramEngine.Runner.DebuggerState);
            }
            ;
            ProgramEngine.Runner.LineNumberChanged += delegate(object o, EventArgs e)
            {
                ProgramEngine.Engine.RaiseLineNumberChangedEvent(ProgramEngine.Runner.CurrentInstruction);
            }
            ;
        }
        public void AddBreakpoint(int lineNumber)
        {
            this.DebuggerAppDomain.DoCallBack(delegate
            {
                ProgramEngine.Runner.Breakpoints.Add(lineNumber);
            }
            );
        }
        public void Continue()
        {
            this.DebuggerAppDomain.DoCallBack(delegate
            {
                ProgramEngine.Runner.Continue();
            }
            );
        }
        public void Pause()
        {
            this.DebuggerAppDomain.DoCallBack(delegate
            {
                ProgramEngine.Runner.Pause();
            }
            );
        }
        private void RaiseDebuggerStateChangedEvent(DebuggerState currentState)
        {
            this.CurrentDebuggerState = currentState;
            EventHandler debuggerStateChanged = this.DebuggerStateChanged;
            if (debuggerStateChanged != null)
            {
                debuggerStateChanged(this, EventArgs.Empty);
            }
        }
        private void RaiseLineNumberChangedEvent(Instruction currentInstruction)
        {
            this.CurrentInstruction = currentInstruction;
            EventHandler lineNumberChanged = this.LineNumberChanged;
            if (lineNumberChanged != null)
            {
                lineNumberChanged(this, EventArgs.Empty);
            }
        }
        public void StepInto()
        {
            this.DebuggerAppDomain.DoCallBack(delegate
            {
                ProgramEngine.Runner.StepInto();
            }
            );
        }
        public void StepOver()
        {
            this.DebuggerAppDomain.DoCallBack(delegate
            {
                ProgramEngine.Runner.StepOver();
            }
            );
        }
        public void Reset()
        {
            this.DebuggerAppDomain.DoCallBack(delegate
            {
                ProgramEngine.Runner.Reset();
            }
            );
        }
        public void RunProgram()
        {
            this.DebuggerAppDomain.DoCallBack(delegate
            {
                ProgramEngine.Runner.RunProgram(false);
            }
            );
        }
        public void RunProgram(bool stopOnFirstInstruction)
        {
            this.DebuggerAppDomain.DoCallBack(delegate
            {
                ProgramEngine.Runner.RunProgram(true);
            }
            );
        }
    }
}
